package call.count;


import org.aopalliance.intercept.MethodInvocation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.core.annotation.AnnotationUtils;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * 业务统计工具类
 */
public class CallCountHelper {

    private static final Logger logger = LoggerFactory.getLogger(CallCountHelper.class);

    public DefaultListableBeanFactory beanFactory;

    private static boolean isInterceptJob = false;

    private static boolean isInterceptJmq = false;

    private static boolean isInterceptJsf = false;

    private static boolean isInterceptController = false;

    // 所有类Map
    public final static Map<String, CallCountClass> allClassMap = new ConcurrentHashMap<>();

    // 拦截的调用统计
    public final static Map<String, CallCountMethod> adviceMethodMap = new ConcurrentHashMap<>();

    // 统计数据包
    public volatile Map<String, CallCountResult> dataPacket;

    @Value("${call.count.platform.id:1}")
    private Long PLATFORM_ID;

    @Value("${APP_ID:1000001}")
    private String SYSTEM_ID;

    @Value("${APP_NAME:未知}")
    private String SYSTEM_NAME;

    @Value("${JDOS_POD_IP:127.0.0.1}")
    private String IP;

    @Value("${call.count.timer.broadcast.period:1}")
    private Long timerBroadcastPeriod;

    @Value("${call.count.timer.boradcost.event.log:true}")
    private Boolean timerBroadcastEventLog;

    @Value("${call.count.timer.boradcost.event.report:true}")
    private Boolean timerBroadcastEventReport;


    // todo unknow
    @Resource
    private List<CallCountBroadcastEvent> callCountBroadcastEvents;

    // 创建一个线程池执行器
    public final static ScheduledThreadPoolExecutor schedule = new ScheduledThreadPoolExecutor(1);

    private final static List<Integer> SUPPORT_CALL_COUNT_TYPE_LIST = new ArrayList<>(Arrays.asList(
            COMPONENT_TYPE_ENUM.JMQ.intValue(),
            COMPONENT_TYPE_ENUM.JSF.intValue(),
            COMPONENT_TYPE_ENUM.JOB.intValue(),
            COMPONENT_TYPE_ENUM.CONTROLLER.intValue()));

    /**
     * 解析需要进行调用统计的组件
     */
    public void analysisCallCountComponent() {
        String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            BeanDefinition bd = beanFactory.getBeanDefinition(beanDefinitionName);
            try {
                if (bd.getBeanClassName() == null) {
                    continue;
                }
                Class<?> clazz = Class.forName(bd.getBeanClassName());
                // 通过反射解析出调用的组件所实现的所有接口的Class对象
                List<Class<?>> allInterfaces = CallCountClassUtils.getAllInterfaces(clazz);
                // 将Class对象转化成名字
                List<String> allInterfacesStringList = CallCountClassUtils.convertClassesToClassNames(allInterfaces);
                // 先判断拦截的组件类型
                if (isInterceptJmq && allInterfacesStringList.contains(CallCountConstant.JMQ_INTERFACE)) {
                    checkClassAndPut(clazz, COMPONENT_TYPE_ENUM.JMQ.intValue());
                } else if (isInterceptJob && allInterfacesStringList.contains(CallCountConstant.JOB_INTERFACE)) {
                    checkClassAndPut(clazz, COMPONENT_TYPE_ENUM.JOB.intValue());
                } else if (isInterceptController && (allInterfacesStringList.contains(CallCountConstant.CONTROLLER_INTERFACE) || AnnotationUtils.findAnnotation(clazz, org.springframework.stereotype.Controller.class) != null)) {
                    checkClassAndPut(clazz, COMPONENT_TYPE_ENUM.CONTROLLER.intValue());
                } // jsf特殊处理
                else if (isInterceptJsf && bd.getBeanClassName().equals(CallCountConstant.JSF_INTERFACE)) {
                    // todo unknow
                    Object o = bd.getPropertyValues().getPropertyValue("ref").getValue();
                    if (o instanceof RuntimeBeanReference) {
                        String ref = ((RuntimeBeanReference) o).getBeanName();
                        BeanDefinition jsfBd = beanFactory.getBeanDefinition(ref);
                        checkClassAndPut(Class.forName(jsfBd.getBeanClassName()), COMPONENT_TYPE_ENUM.JSF.intValue());
                    } else {
                        logger.error("[CALL_COUNT_LOG] CallCountHelper analysisCallCountComponent jsf 解析失败 bd.getBeanClassName():{}", bd.getBeanClassName());
                    }
                } else {
                    checkClassAndPut(clazz, COMPONENT_TYPE_ENUM.UNKNOWN.intValue());
                }
            } catch (Throwable e) {
                logger.error("[CALL_COUNT_LOG] CallCountHelper analysisCallCountComponent bd.getBeanClassName():{} error:{}", bd.getBeanClassName(), e.getMessage(), e);
            }
        }
    }

    /**
     * 将需要拦截的类及其方法信息存放起来
     * @param clazz
     * @param type
     */
    private void checkClassAndPut(Class<?> clazz, Integer type) {
        try {
            // 全类名
            String className = clazz.getName();
            CallCountClass callCountClass = CallCountHelper.allClassMap.get(className);
            if (callCountClass == null || !SUPPORT_CALL_COUNT_TYPE_LIST.contains(callCountClass.getType())) {
                callCountClass = new CallCountClass();
                callCountClass.setClassName(className);
                callCountClass.setType(type);
                callCountClass.setIsNeedAdvice(SUPPORT_CALL_COUNT_TYPE_LIST.contains(type) ? IS_NEED_ADVICE_ENUM.NEED.intValue() : IS_NEED_ADVICE_ENUM.NOT_NEED.intValue());

                // 得到当前bean有哪些方法
                Method[] methods = clazz.getDeclaredMethods();
                List<CallCountMethod> callCountMethods = new ArrayList<>();
                Map<String, CallCountMethod> methodNames = new HashMap<>();
                for (Method method : methods) {
                    // 修饰符
                    int modifiers = method.getModifiers();
                    String methodName = method.getName();
                    if (methodName == null || methodName.contains("$") || Modifier.isPrivate(modifiers) || methodNames.containsKey(methodName)) {
                        continue;
                    }
                    CallCountMethod callCountMethod = new CallCountMethod();
                    callCountMethod.setClassName(className);
                    callCountMethod.setMethodName(methodName);
                    callCountMethod.setCount(new AtomicInteger(0));
                    callCountMethod.setType(type);
                    callCountMethod.setTypeName(COMPONENT_TYPE_ENUM.getLabelByValue(type));
                    callCountMethods.add(callCountMethod);
                    methodNames.put(methodName, callCountMethod);
                }
                callCountClass.setMethodNames(methodNames);
                callCountClass.setMethods(callCountMethods);
                CallCountHelper.allClassMap.put(className, callCountClass);
            } else {
                logger.warn("[CALL_COUNT_LOG] CallCountHelper checkClassAndPut isRepeat callCountClass:{}", callCountClass);
            }
        } catch (Throwable e) {
            logger.error("[CALL_COUNT_LOG] CallCountHelper checkClassAndPut error clazz.getName:{}", clazz.getName(), e);
        }
    }

    public boolean pointcutMatches(Method method, Class<?> targetClass) {
        boolean matchesFlag = false;
        String className = targetClass.getName();
        String methodName = method.getName();
        CallCountClass callCountClass = CallCountHelper.allClassMap.get(className);
        // 判断当前请求的class和method是否在拦截名单里，如果没有，则不拦截，如果有，则拦截返回true
        if (callCountClass != null && IS_NEED_ADVICE_ENUM.NEED.intValue().equals(callCountClass.getIsNeedAdvice()) && callCountClass.getMethodNames().containsKey(methodName)) {
            callCountClass.setAdviceSuccess(ADVICE_SUCCESS_ENUM.SUCCESS.intValue());
            CallCountMethod callCountMethod = callCountClass.getMethodNames().get(methodName);
            adviceMethodMap.put(callCountMethod.buildFilterKey(), callCountMethod);
            matchesFlag = true;
        }
        return matchesFlag;
    }


    /**
     * 检查四种组件是否需要拦截
     */
    public void checkIsIntercept() {
        try {
            Class.forName(CallCountConstant.JMQ_INTERFACE);
            isInterceptJmq = true;
        } catch (Throwable e) {
            logger.warn("[CALL_COUNT_LOG] CallCountHelper checkIsIntercept INTERFACE:{} isIntercept:{} error:{}",
                    CallCountConstant.JMQ_INTERFACE, isInterceptJmq, e.getMessage());
        }

        try {
            Class.forName(CallCountConstant.JSF_INTERFACE);
            isInterceptJsf = true;
        } catch (Throwable e) {
            logger.warn("[CALL_COUNT_LOG] CallCountHelper checkIsIntercept INTERFACE:{} isIntercept:{} error:{}",
                    CallCountConstant.JSF_INTERFACE, isInterceptJsf, e.getMessage());
        }

        try {
            Class.forName(CallCountConstant.JOB_INTERFACE);
            isInterceptJob = true;
        } catch (Throwable e) {
            logger.warn("[CALL_COUNT_LOG] CallCountHelper checkIsIntercept INTERFACE:{} isIntercept:{} error:{}",
                    CallCountConstant.JOB_INTERFACE, isInterceptJob, e.getMessage());
        }

        try {
            Class.forName(CallCountConstant.CONTROLLER_INTERFACE);
            isInterceptController = true;
        } catch (Throwable e) {
            logger.warn("[CALL_COUNT_LOG] CallCountHelper checkIsIntercept INTERFACE:{} isIntercept:{} error:{}",
                    CallCountConstant.CONTROLLER_INTERFACE, isInterceptController, e.getMessage());
        }
    }

    public void adviceInvocationCallCount(MethodInvocation invocation) {
        if (dataPacket == null) {
            logger.warn("[CALL_COUNT_LOG] CallCountHelper adviceInvocationCallCount but dataPacket is null");
            return;
        }
        String methodName = invocation.getMethod().getName();
        String className = invocation.getThis().getClass().getName();
        String key = className + CallCountConstant.SPLIT_CHAR + methodName;
        CallCountResult callCountResult = dataPacket.get(key);
        if (callCountResult != null) {
            callCountResult.getCount().incrementAndGet();
        }
    }

    /**
     * 数据包重置
     */
    public void resetDataPacket() {
        Map<String, CallCountResult> _dataPacket = new ConcurrentHashMap<>();
        for (String key : adviceMethodMap.keySet()) {
            CallCountResult callCountResult = new CallCountResult();
            CallCountMethod callCountMethod = adviceMethodMap.get(key);
            callCountResult.setClassName(callCountMethod.getClassName());
            callCountResult.setMethodName(callCountMethod.getMethodName());
            callCountResult.setType(callCountMethod.getType());
            callCountResult.setCount(new AtomicInteger(0));

            callCountResult.setSystemId(SYSTEM_ID);
            callCountResult.setSystemName(SYSTEM_NAME);
            callCountResult.setIp(IP);
            callCountResult.setStatStartDate(CallCountDateUtils.getReportMinuteTime());
            _dataPacket.put(key, callCountResult);
        }
        this.dataPacket = _dataPacket;
    }

    /**
     * 定时调用统计结果
     */
    public void timerPrint() {
        // 创建线程的方式
        Runnable task = () -> {
            try {
                resultBroadcast();
            } catch (Throwable e) {
                logger.error("[CALL_COUNT_LOG] CallCountHelper timerPrint error:{}", e.getMessage(), e);
            }
        };
        long delay = 60000 - System.currentTimeMillis() % 60000;
        long period = timerBroadcastPeriod * CallCountConstant.PERIOD_MIN_STEP;
        schedule.scheduleAtFixedRate(task, delay, period, TimeUnit.MILLISECONDS);
    }

    /**
     * 广播统计数据
     */
    public void resultBroadcast() {
        if (callCountBroadcastEvents != null && !callCountBroadcastEvents.isEmpty()) {
            Map<String, CallCountResult> _dataPacket = dataPacket;
            resetDataPacket();

            String currentDateTimeString = CallCountDateUtils.getNowDateString();
            for (CallCountBroadcastEvent callCountBroadcastEvent : callCountBroadcastEvents) {
                // todo unknow UMP
                CallerInfo callerInfo = Profiler.registerInfo(CallCountConstant.UMP_CALLCOUNTHELPER_ONEVENT + callCountBroadcastEvent.getClass().getSimpleName(), SYSTEM_NAME, false, true);
                try {
                    if ((!timerBroadcastEventLog && callCountBroadcastEvent instanceof CallCountBroadcastEventLog)
                            || (!timerBroadcastEventReport && callCountBroadcastEvent instanceof CallCountBroadcastEventReport)) {
                        continue;
                    }
                    callCountBroadcastEvent.onEvent(_dataPacket.values());
                    logger.info("[CALL_COUNT_LOG] CallCountHelper resultBroadcast success currentDateTimeString:{} eventImplClass:{}",
                            currentDateTimeString, callCountBroadcastEvent.getClass().getSimpleName());
                } catch (Throwable t) {
                    Profiler.functionError(callerInfo);
                    logger.error("[CALL_COUNT_LOG] CallCountHelper resultBroadcast eventImplClass:{} error:{}", callCountBroadcastEvent.getClass().getName(), t.getMessage());
                } finally {
                    Profiler.registerInfoEnd(callerInfo);
                }
            }
        }
    }

    public String printCallCountResult() {
        String currentDateTimeString = CallCountDateUtils.getNowDateString();
        logger.info("[CALL_COUNT_LOG] CallCountHelper printCallCountResult {}start =================================", currentDateTimeString);
        logger.info("[CALL_COUNT_LOG] CallCountHelper printCallCountResult {} 全部注册的Bean:{}", currentDateTimeString,
                CallCountStringUtils.collectionToString(allClassMap.values().stream()
                        .map(CallCountClass::getClassName)
                        .distinct()
                        .collect(Collectors.toList())));
        logger.info("[CALL_COUNT_LOG] CallCountHelper printCallCountResult {} 需要拦截的Bean:{}", currentDateTimeString,
                CallCountStringUtils.collectionToString(allClassMap.values().stream()
                        .filter(item -> IS_NEED_ADVICE_ENUM.NEED.intValue().equals(item.getIsNeedAdvice()))
                        .map(CallCountClass::getClassName)
                        .distinct()
                        .collect(Collectors.toList())));
        logger.info("[CALL_COUNT_LOG] CallCountHelper printCallCountResult {} 拦截成功的Bean:{}", currentDateTimeString,
                CallCountStringUtils.collectionToString(allClassMap.values().stream()
                        .filter(item -> IS_NEED_ADVICE_ENUM.NEED.intValue().equals(item.getIsNeedAdvice())
                                && ADVICE_SUCCESS_ENUM.SUCCESS.intValue().equals(item.getAdviceSuccess()))
                        .map(CallCountClass::getClassName)
                        .distinct()
                        .collect(Collectors.toList())));
        logger.info("[CALL_COUNT_LOG] CallCountHelper printCallCountResult {} 拦截失败的Bean:{}",
                currentDateTimeString, CallCountStringUtils.collectionToString(allClassMap.values().stream()
                        .filter(item -> IS_NEED_ADVICE_ENUM.NEED.intValue().equals(item.getIsNeedAdvice())
                                && ADVICE_SUCCESS_ENUM.FAIL.intValue().equals(item.getAdviceSuccess()))
                        .map(CallCountClass::getClassName)
                        .distinct()
                        .collect(Collectors.toList())));
        logger.info("[CALL_COUNT_LOG] CallCountHelper printCallCountResult {} 未拦截的Bean:{}", currentDateTimeString,
                CallCountStringUtils.collectionToString(allClassMap.values().stream()
                        .filter(item -> IS_NEED_ADVICE_ENUM.NOT_NEED.intValue().equals(item.getIsNeedAdvice())
                                || ADVICE_SUCCESS_ENUM.FAIL.intValue().equals(item.getAdviceSuccess()))
                        .map(CallCountClass::getClassName)
                        .distinct()
                        .collect(Collectors.toList())));
        logger.info("[CALL_COUNT_LOG] CallCountHelper printCallCountResult {} 拦截成功的调用统计:{}", currentDateTimeString,
                CallCountStringUtils.collectionToString(adviceMethodMap.values().stream()
                        .map(method -> String.join(CallCountConstant.SPLIT_CHAR, method.getClassName(), method.getMethodName(), method.getTypeName(), method.getCount().toString()))
                        .collect(Collectors.toList())));
        logger.info("[CALL_COUNT_LOG] CallCountHelper printCallCountResult {} 存在调用的统计:{}", currentDateTimeString,
                CallCountStringUtils.collectionToString(adviceMethodMap.values().stream()
                        .filter(method -> method.getCount().get() > 0)
                        .map(method -> String.join(CallCountConstant.SPLIT_CHAR, method.getClassName(), method.getMethodName(), method.getTypeName(), method.getCount().toString()))
                        .collect(Collectors.toList())));
        logger.info("[CALL_COUNT_LOG] CallCountHelper printCallCountResult {} 全部方法名称:{}", currentDateTimeString,
                CallCountStringUtils.collectionToString(allClassMap.values().stream()
                        .filter(item -> IS_NEED_ADVICE_ENUM.NEED.intValue().equals(item.getIsNeedAdvice()))
                        .filter(item -> item.getMethods() != null)
                        .flatMap(item -> item.getMethods().stream())
                        .map(CallCountMethod::getMethodName)
                        .distinct()
                        .collect(Collectors.toList())));
        logger.info("[CALL_COUNT_LOG] CallCountHelper printCallCountResult {}end =================================", currentDateTimeString);
        return currentDateTimeString;
    }

}